#ifndef SCON_RESULT_H_
#define SCON_RESULT_H_

#include <windows.h>

#ifdef __SCON_TEST__
#include "gtest/gtest_prod.h"
#endif

#ifdef __SCON_DEBUG__
#include <iostream>
#include <iomanip>
#endif

namespace SCon {

class FutureResult {
/*
класс FutureResult предназначен для реализации отложенной доставки результата. (первоночально объект не представляет ценности и достоверный результат может быть получен в любое время вызовом функции GetResult()
Особенность данного класса заключается в том, что копирование объектов этого класса является разрушающим.
Некоторые функции этого класса перегружены и имеют дополнительный параметр std::string* errorMessage - указатель на строку в которую можно сохранить ошибку. В случае использования этих функций при передаче в них не нулевого указателя на строку имеется возможность получить текст ошибки.
*/
#ifdef __SCON_TEST__
	friend class ResultTest;
	FRIEND_TEST(ResultTest, FutureResult);
	FRIEND_TEST(ResultTest, FutureResult_CopyConstructorTest);
	FRIEND_TEST(ResultTest, FutureResult_AssignmentOperatorTest);
	FRIEND_TEST(ResultTest, FutureResult_InitializeTest);
#endif

public:
/*
Разные версиии конструктора класса FutureResult принимает следующие параметры:
- event - дескриптор события, активация которого происходит при окончании операции, должен быть корректным.
- dataSize - размер данных подлежащих обработке во время операции, должен быть больше нуля
- actualDataSize - указатель на действительный размер принятых/отправленных данных
- buf - буфер данных, может быть равен 0.
- errorMessage - указатель на строку куда можно сохранить текст ошибки, может быть равным 0
Следует учитывать также, что в случае если задан указатель на буффер - объект класса не владеет им (буффером),
в случае же если в конструктор в качестве указателя на буффер был передан 0 (или была вызвана версия конструктора без параметра buf), то объект создаст внутренний буфер, которым он будет владеть.
В последнем случае получить результат можно либо передав указатель на буфер в функцию FutureResult::GetResult() либо явно вызвав функцию
FutureResult::GetCopyResult(). В обоих случаях произойдет копирование буфера, а в случае с FutureResult::GetCopyResult() еще и передача владения буфером.
Если необходимо получить доступ к буфферу объекта класса, не передавая указатель на него во время конструирования, необходимо воспользоваться комбинацией вызовов конструктора по умолчанию и функции FutureResult::Initialize(), которая возвращает указатель на буффер. Однако следует помнить, что FutureResult::Initilalize() не передает владения.
*/
	FutureResult (HANDLE event, int dataSize, const int* actualDataSize);
	FutureResult (HANDLE event, int dataSize, const int* actualDataSize, unsigned char* buf);
	FutureResult (HANDLE event, int dataSize, const int* actualDataSize, unsigned char* buf, std::string* errorMessage);
	FutureResult ();
	~FutureResult();

/*
Конструктор копирования класса FutureResult работает так, что копируемый объект разрушается. Это необходимо для передачи владения (если он есть) буфером и для избежания возможный ошибок при манипулировании двумя объектами отвечающими за выдачу результата одной и той же операции.
*/
	FutureResult(FutureResult& otherRes);

/*
Вызов оператора копирования, так же как и вызов копирующего конструктора разрушает копируемый объект. Следует учитывать также, что если объект вызывающий оператор присваивания владел буффером - этот буффер уничтожается и все возможные ссылки и указатели на него становятся невалидными.
*/
	FutureResult& operator=(FutureResult& otherRes);

/*
Группа перегруженных Функций
	unsigned char* Initialize();
инициализирует объект класса FutureResult
Принимаемые параметры:
- event - дескриптор события, активация которого происходит при окончании операции, должен быть корректным.
- dataSize - размер данных, подлежащих обработке во время операции, должен быть больше 0.
- actualDataSize - указатель на действительный размер принятых/отправленных данных
- buf - буффер данных, может быть равен 0.
- errorMessage - указатель на строку куда можно сохранить текст ошибки, может быть равным 0
Возвращаемое значение:
Указатель на буффер, где будет содержаться достоверный результат по окончании операции. В случае если buf был задан 0 - возвращается указатель на буффер созданный самим оъектом класса FutureResult<>, однако владение остается за объектом класса, а не за вызывающим кодом! В случае, если buf  был задан вызывающим кодом, возвращается указатель на него.
*/
	unsigned char* Initialize(HANDLE event, int dataSize,  const int* actualDataSize);
	unsigned char* Initialize(HANDLE event, int dataSize,  const int* actualDataSize, unsigned char* buf);
	unsigned char* Initialize(HANDLE event, int dataSize,  const int* actualDataSize, unsigned char* buf, std::string* errorMessage);

/*
группа Функций
	bool IsDone();
Принимаемые параметры:
- errorMessage - указатель на строку куда можно сохранить текст ошибки, может быть равным 0
Возвращает true если операция закончена и достоверные данные доступны.
Следует заметить что ошибки при выполнении этой функции маловероятны и связаны с поведением функции WINAPI WaitForSingleObject()
*/
	bool IsDone() const;
	bool IsDone(std::string* errorMessage) const;

/*
Функция:
	int GetResult();
Возвращает достоверный результат операции.
Принимаемые параметры:
- buf - если передан указатель на буфер, то происходит копирование результатa операции в указанный буфер(размер буфeра должен быть подходящим!). Может быть передан 0.
- errorMessage - указатель на строку куда можно сохранить текст ошибки, может быть равным 0
Возвращаемые параметры:
количество считанных/переданных байт.
Следует заметить что ошибки при выполнении этой функции маловероятны и связаны с поведением функции WINAPI WaitForSingleObject()
*/
	int GetResult() const;
	int GetResult(unsigned char* buf) const;
	int GetResult(unsigned char* buf, std::string* errorMessage) const;

/*
Функция
	unsigned char* GetCopyResult();
Возвращает указатель на копию буффера, содержащего достоверный результат операции.
Владение буффером на который указывает возвращемый указатель переходи вызывающему коду, который должен позаботится о вызове функции delete[],
когда надобность в буфере отпадет.
*/
	unsigned char* GetCopyResult() const;

private:
	HANDLE operationIsDone_;
	unsigned char* message_;
	int dataSize_;
	const int* actualDataSize_;
	bool isOwnedToBuffer_;

/*
Функция
	void CopyBuf(unsigned char* begin1 , usigned char end1, unsigned char* begin2);
копирует массив bebin1 до элемента end1 включительно в массив begin2
*/
	void CopyBuf(unsigned char* begin1 , unsigned char* end1, unsigned char* begin2) const;

/*
Функция
	static void errorHandler(bool operationState, std::string* errorMessage);
является вспомогательной и предназначена для для генерации сообщений обо ошибках
Принимаемые параметры:
- operationState - булевский параметр, сигнализирующей об успешном(или нет) прохождении операции
- errorMessage - указатель на строку, куда можно сохранить текст ошибки в случае необходимости
*/
	static void ErrorHandler(bool operationState, std::string* errorMessage);
};//end of declaration class FutureResult

FutureResult::FutureResult(): operationIsDone_(INVALID_HANDLE_VALUE),
								message_(0),
								dataSize_(0),
								actualDataSize_(0),
								isOwnedToBuffer_(false)
{}

FutureResult::FutureResult(HANDLE event, int dataSize, const int* actualDataSize){
	Initialize(event, dataSize, actualDataSize);
}// end of FutureResult(HANDLE Event, int dataSize, unsigned char* buf)

FutureResult::FutureResult(HANDLE event, int dataSize, const int* actualDataSize, unsigned char* buf){
	Initialize(event, dataSize, actualDataSize, buf);
}// end of FutureResult(HANDLE Event, int dataSize, unsigned char* buf)

FutureResult::FutureResult(HANDLE event, int dataSize, const int* actualDataSize, unsigned char* buf, std::string* errorMessage){
	Initialize(event, dataSize, actualDataSize, buf, errorMessage);
}// end of FutureResult(HANDLE Event, int dataSize, unsigned char* buf)

FutureResult::~FutureResult() {
	if (isOwnedToBuffer_) {
		delete[] message_;
	}/* end of if */
}//end of FutureResult::~FutureResult()

FutureResult::FutureResult(FutureResult& otherRes): operationIsDone_(otherRes.operationIsDone_),
													message_(otherRes.message_),
													dataSize_(otherRes.dataSize_),
													actualDataSize_(otherRes.actualDataSize_),
													isOwnedToBuffer_(otherRes.isOwnedToBuffer_)
{
	otherRes.operationIsDone_ = INVALID_HANDLE_VALUE;
	otherRes.message_ = 0;
	otherRes.dataSize_ = 0;
	otherRes.actualDataSize_ = 0;
	otherRes.isOwnedToBuffer_ = false;
}//end of FutureResult::FutureResult(FutureResult&)

FutureResult& FutureResult::operator=(FutureResult& otherRes) {
#ifdef __SCON_DEBUG__
#define _D_Assignment_ 1
#else
#define _D_Assignment_ 0
#endif

//уничтожаем буффер, которым владели
	if (isOwnedToBuffer_) {
		delete[] message_;

		if (_D_Assignment_) {
			std::cout << "buffer destroied\n";
		}/* end of if */
	}/* end of if */

//подменяем данные
	operationIsDone_ = otherRes.operationIsDone_;
	message_ = otherRes.message_;
	dataSize_ = otherRes.dataSize_;
	actualDataSize_ = otherRes.actualDataSize_;
	isOwnedToBuffer_ = otherRes.isOwnedToBuffer_;

//очищаем копируемый объект
	otherRes.operationIsDone_ = INVALID_HANDLE_VALUE;
	otherRes.message_ = 0;
	otherRes.dataSize_ = 0;
	otherRes.actualDataSize_ = 0;
	otherRes.isOwnedToBuffer_ = false;

	return *this;
}//end of FutureResult& FutureResult::operator= ()

unsigned char* FutureResult::Initialize(HANDLE event,
										int dataSize,
										const int* actualDataSize) {
	if (event == INVALID_HANDLE_VALUE) {
		return 0;
	}/* end of if */

	if (dataSize <= 0) {
		return 0;
	}/* end of if */

	if (actualDataSize == 0) {
		return 0;
	}/* end of if */

	operationIsDone_ = event;
	dataSize_ = dataSize;
	actualDataSize_ = actualDataSize;

	message_ = new unsigned char[dataSize_];
	isOwnedToBuffer_ = true;
	return message_;

}//end of unsigned char* FutureResult::Initialize()

unsigned char* FutureResult::Initialize(HANDLE event,
										int dataSize,
										const int* actualDataSize,
										unsigned char* buf) {
	if (event == INVALID_HANDLE_VALUE) {
		return 0;
	}/* end of if */

	if (dataSize <= 0) {
		return 0;
	}/* end of if */

	if (actualDataSize == 0) {
		return 0;
	}/* end of if */

	operationIsDone_ = event;
	dataSize_ = dataSize;
	actualDataSize_ = actualDataSize;

	if (buf == 0) {
		message_ = new unsigned char[dataSize_];
		isOwnedToBuffer_ = true;
		return message_;
	}/* end of if */

	message_ = buf;
	isOwnedToBuffer_ = false;
	return message_;
}//end of unsigned char* FutureResult::Initialize()

unsigned char* FutureResult::Initialize(HANDLE event,
										int dataSize,
										const int* actualDataSize,
										unsigned char* buf,
										std::string* errorMessage) {
	if (event == INVALID_HANDLE_VALUE) {
		//(errorMessage != 0) ? (*errorMessage = "Bad parameter 'event'") : (errorMessage = 0);
		if (errorMessage != 0) {
			*errorMessage = "Bad parameter 'event'";
		}/* end of if */
		return 0;
	}/* end of if */

	if (dataSize <= 0) {
		if (errorMessage != 0) {
			*errorMessage = "Bad parameter 'dataSize'";
		}/* end of if */
		return 0;
	}/* end of if */

	if (actualDataSize == 0) {
		if (errorMessage != 0) {
			*errorMessage = "Bad parameter 'actualDataSize'";
		}/* end of if */
		return 0;
	}/* end of if */

	operationIsDone_ = event;
	dataSize_ = dataSize;
	actualDataSize_ = actualDataSize;

	if (buf == 0) {
		message_ = new unsigned char[dataSize_];
		isOwnedToBuffer_ = true;
		return message_;
	}/* end of if */

	message_ = buf;
	isOwnedToBuffer_ = false;
	return message_;
}//end of unsigned char* FutureResult::Initialize()

bool FutureResult::IsDone() const {
	DWORD res = WaitForSingleObject(operationIsDone_, 0);
	
	if (res == WAIT_FAILED) {
		return false;
	}/* end of if */

	if (res == WAIT_TIMEOUT) {
		return false;
	}/* end of if */

	return true;
}//end of bool FutureResult::IsDone ()

bool FutureResult::IsDone(std::string* errorMessage) const {
	DWORD res = WaitForSingleObject(operationIsDone_, 0);
	
	if (res == WAIT_FAILED) {
		ErrorHandler(false, errorMessage);
		return false;
	}/* end of if */

	if (res == WAIT_TIMEOUT) {
		return false;
	}/* end of if */

	return true;
}//end of bool FutureResult::IsDone ()

int FutureResult::GetResult() const {
	DWORD res = WaitForSingleObject(operationIsDone_, INFINITE);

	if (res == WAIT_FAILED) {
		return -1;
	}/* end of if */

	return *actualDataSize_;
}//end of int FutureResult::GetResult()

int FutureResult::GetResult(unsigned char* buf) const {
	DWORD res = WaitForSingleObject(operationIsDone_, INFINITE);

	if (res == WAIT_FAILED) {
		return -1;
	}/* end of if */

	if (buf != 0) {
		CopyBuf(message_, message_ + *actualDataSize_, buf); 
	}/* end of if */

	return *actualDataSize_;
}//end of int FutureResult::GetResult()

int FutureResult::GetResult(unsigned char* buf, std::string* errorMessage) const {
	DWORD res = WaitForSingleObject(operationIsDone_, INFINITE);

	if (res == WAIT_FAILED) {
		ErrorHandler(false, errorMessage);
		return -1;
	}/* end of if */

	if (buf != 0) {
		CopyBuf(message_, message_ + *actualDataSize_, buf); 
	}/* end of if */

	return *actualDataSize_;
}//end of int FutureResult::GetResult()

unsigned char* FutureResult::GetCopyResult() const {
	unsigned char* copyBuf = new unsigned char[*actualDataSize_];
	CopyBuf(message_, message_ + *actualDataSize_, copyBuf);

	return copyBuf;
}//end of unsigned char* FutureResult::GetCopyResult()

void FutureResult::CopyBuf(unsigned char* begin1, unsigned char* end1, unsigned char* begin2) const {
	unsigned char* it1 = begin1;
	unsigned char* it2 = begin2;
	while(it1 != end1) {
		*it2 = *it1;
		++it1;
		++it2;
	}//end of while
}//end of void FutureResult::CopyBuf()

void FutureResult::ErrorHandler(bool operationState, std::string* errorMessage) {
	if (operationState == true) {
		return;
	}/* end of if */

	if (operationState == false && errorMessage != 0) {
		int errorCode = GetLastError();
		char buf[500];
		FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode,
						MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
						buf, 500, NULL);
		*errorMessage = buf;
	}/* end of if */
}//end of void FutureResult::ErrorHandler()

} /* SCon */ 

#endif /* end of include guard: SCON_RESULT_H_ */

